package com.nulldev.util.uri.nget;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedList;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.io.ByteArrayInOutStream;
import com.nulldev.util.io.FileUtil;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.java.swing.SwingUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.math.lNumber;
import com.nulldev.util.other.ByteUtils;
import com.nulldev.util.threading.legacy.ThreadExecutor;
import com.nulldev.util.uri.URIUtil;
import com.nulldev.util.uri.URIUtil2;

public class nGet extends Thread {

	/**
	 * 1.0.0 - Inital Release <br>
	 * 1.0.1 - Chunked downloading + YTDownloader speed fix
	 */
	public static final String RELEASE = "1.0.1";
	
	private final LinkedList<nGetEvent> events = new LinkedList<nGetEvent>();
	
	private URL url;
	private File location = FileUtil.tempDir();
	private boolean skipOverride;
	private String overrideName;
	private boolean logDownloadStatus;
	private static final Logger log = LoggerFactory.getLogger(false);
	public nGet(final URL toDownload) {
		this.url = toDownload;
		this.status.setValue(0);
		this.status.setStringPainted(true);
		this.status.setString("Downloading...");
	}
	
	public boolean registerEvent(final nGetEvent nge) {
		final boolean result = this.events.add(nge);
		if (true) {
			nge.assignGet(this);
		}
		return result;
	}
	
	private final JProgressBar status = new JProgressBar();
	
	public final Logger getLogger() {
		return this.log;
	}
	
	public JProgressBar getBar() {
		return this.status;
	}
	
	public void disableOverridePrompt(final boolean state) {
		this.skipOverride = state;
	}
	
	public boolean isOverrideSkipped() {
		return this.skipOverride;
	}
	
	public boolean logDownloadStatus() {
		return this.logDownloadStatus;
	}
	
	public void setLogDownloadStatus(final boolean state) {
		this.logDownloadStatus = state;
	}
	
	public void download() {
		this.start();
	}
	
	public boolean FAILED = false;
	private final FinalBypass<Boolean> finished = new FinalBypass<Boolean>(false);
	private final FinalBypass<Boolean> started = new FinalBypass<Boolean>(false);
	
	//private long timeStart, elapsedTime, atd, timeLeft;
	
	@Override
	public void run() {
		final FinalBypass<Boolean> failed = new FinalBypass<Boolean>(false);
		
		if (!URIUtil.isValid(this.url)) {
			log.error("Stopped download. Invalid URL.");
			interrupt();
			FAILED = true;
			return;
		}
		if (location == null || !location.isDirectory()) {
			log.error("Stopped download. Invalid directory.");
			interrupt();
			FAILED = true;
			return;
		}
		
		ThreadExecutor.execute(new Runnable() {
			
			@Override
			public void run() {
				try {
					final HttpURLConnection conn = (HttpURLConnection) nGet.this.url.openConnection();
					conn.setRequestProperty("User-agent", URIUtil2.RandomUserAgent.getRandomUserAgent());//"Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0 " + MathUtil.prevRand());
					conn.connect();
					final int length = conn.getContentLength();
					if (length == -1) {
						log.warn("Did not get a size, using an alternative progress bar.");
						status.setIndeterminate(true);
						status.setStringPainted(true);
						status.setString("Unknown size. Downloading...0B");
					}else {
						status.setMaximum(length);
					}
					
					final int response = conn.getResponseCode();
					if (!MathUtil.between(response, 200, 300)) {
						log.error("Response code wasn't 'OK/200'!, stopping... [response=" + response + ",url=" + nGet.this.url + "]");
						for (final nGetEvent nge : (LinkedList<nGetEvent>) nGet.this.events.clone()) {
							nge.onError();
						}
						failed.setObject(true);
						FAILED = failed.getObject();
						return;
					}
					
					log.debug("Starting download...");
					started.setObject(true);
					for (final nGetEvent nge : (LinkedList<nGetEvent>) nGet.this.events.clone()) {
						nge.onStart(length);
					}
					// TODO: Port to nGet2.
			        final BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
			        final File out = FileUtil.safeCreate(nGet.this.location.getAbsolutePath() + "/" + (nGet.this.overrideName == null ? URIUtil.getName(nGet.this.url.toExternalForm()) : nGet.this.overrideName));
			        if (!nGet.this.skipOverride && out.exists()) {
			        	try {
				        	final int option = JOptionPane.showConfirmDialog(null, "File already exists, would you like to override it?", "nGet: File Exists", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
				        	if (option != JOptionPane.YES_OPTION) {
				        		log.error("User chose not to override file, stopping...");
				        		failed.setObject(true);
				        		FAILED = failed.getObject();
				        		return;
				        	}
			        	}catch (RuntimeException e) {
			        		log.warn("Failed to create prompt, overriding...");
			        	}
			        }
			        if (out.exists()) {
			        	out.delete();
			        	out.createNewFile();
			        }
			        final FileOutputStream fout = new FileOutputStream(out);
			        log.debug("Download armed.");
			        
			        final int bufferSize = 512000;
			        byte data[] = ByteUtils.create(bufferSize);
			        long dataDownloaded = 0;
			        int count;
			        float boff = 0;
			        int printed = 0;
			        final String tX = (length != -1 ? FileUtil.convertToStringRepresentation(length) : "N/A");
			        if (length != -1) {
			        	log.debugE("==> Downloading file '" + out.getName() + "' [");
			        }
			       // nGet.this.timeStart = System.nanoTime();
			        while ((count = in.read(data, 0, bufferSize)) != -1) {
			            fout.write(data, 0, count);
			            dataDownloaded += count;
			         //   nGet.this.elapsedTime = System.nanoTime() - nGet.this.timeStart;
			            if (length != -1) {
				            if (length > 0 && bufferSize > 0) {
								for (final nGetEvent nge : (LinkedList<nGetEvent>) nGet.this.events.clone()) {
									nge.onDownload(dataDownloaded, -1);
								}
				            	nGet.this.status.setValue((int) dataDownloaded);
				            	if (tX != "N/A") {
				            		nGet.this.status.setString("Downloading..." + FileUtil.convertToStringRepresentation(dataDownloaded) + "/" + tX);
				            	}else {
				            		nGet.this.status.setString("Downloading..." + FileUtil.convertToStringRepresentation(dataDownloaded));
				            	}
				            }
			            }
			           // if (length != -1) {
		            //		nGet.this.atd = (nGet.this.elapsedTime * length / dataDownloaded);
		            	//	nGet.this.timeLeft = nGet.this.atd - elapsedTime;
			          //  }
		            	if (nGet.this.logDownloadStatus && length != -1) {
		            		//System.out.println((float) ((dataDownloaded * 10) / length) * 10);
		            		final float percent = (float) ((dataDownloaded * 10) / length) * 10;
		            		//log.binfo(String.valueOf((float) (dataDownloaded / length) * 100));
		            		if ((percent == 0 || percent == 10 || percent == 20 || 
		            				percent == 30 || percent == 40 || percent == 50 || 
		            				percent == 60 || percent == 70 || percent == 80 || 
		            				percent == 90 || percent == 100) && boff == 50 && printed < 11) {
		            			log.debugE("=");
		            			printed += 1;
		            		}
	            			if (boff > 50) {
	            				boff = 0;
	            			}else {
	            				boff += 1;
	            			}
							for (final nGetEvent nge : (LinkedList<nGetEvent>) nGet.this.events.clone()) {
								nge.onDownload(dataDownloaded, percent);
							}
		            		//log.binfo("Downloading..." + FileUtil.convertToStringRepresentation(dataDownloaded));
		            	}else {
		            		log.debug("Downloading..." + FileUtil.convertToStringRepresentation(dataDownloaded));
		            	}
			        }
			        
			        fout.close();
			        in.close();
			        
			        if (length != -1) {
			        	log.debugE("]" + JVMUtil.lineSep());
			        }
			        
					for (final nGetEvent nge : (LinkedList<nGetEvent>) nGet.this.events.clone()) {
						nge.onFinish();
					}
			        
			        log.debug("Finished.");
					
					finished.setObject(true);
				}catch (Exception e) {
					e.printStackTrace();
					log.error("Failed to download! Stopping...");
					failed.setObject(true);
					FAILED = failed.getObject();
					return;
				}
			}
		});
		FAILED = failed.getObject();
	}
	
	public void setDownloadLocation(final File f) {
		if (!f.isDirectory()) {
			log.error("Failed to set download location, not a directory.");
			return;
		}
		this.location = f;
	}

	public void overrideName(final String string) {
		this.overrideName = string;
	}

	public void waitTillFinished() {
		while (true) {
			if (finished.getObject()) {
				break;
			}
			if (FAILED) {
				break;
			}
			try {
				Thread.sleep(150L);
			} catch (InterruptedException e) {
			}
		}
	}

	public void setDirectory(final File folder) {
		this.location = folder;
	}

	public String overrideName() {
		return this.overrideName;
	}

	public File getDirectory() {
		return this.location;
	}

	//public long getTimeLeft() {
	//	return this.timeLeft;
	//}
}
